package hikari.gui.assets;

import com.gooddies.ballontips.ToolTipHandler;
import com.gooddies.persistence.Properties;
import com.gooddies.swing.hFileChooser;
import com.gooddies.wiring.Wiring;
import hikari.utils.Utils;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.util.Enumeration;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import javax.swing.JFileChooser;
import javax.swing.JOptionPane;
import javax.swing.SwingUtilities;
import javax.swing.SwingWorker;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.DefaultTreeModel;
import javax.swing.tree.TreeNode;
import javax.swing.tree.TreePath;
import org.apache.commons.lang3.StringEscapeUtils;

/**
 *
 * @author sad
 */
public class AssetArchiver extends javax.swing.JDialog {

    public static String ASSET_FILE_EXTENSION = ".hasset";
    private int currentProcessedFile = 0;
    private boolean inProcess = false;

    public AssetArchiver(java.awt.Frame parent, boolean modal) {
        super(parent, modal);
        initComponents();
        fillTree();
        loadUserData();
        assetTree.setCellRenderer(Wiring.getComponent(AssetsTreeRenderer.class));
        pack();
        setLocationRelativeTo(null);

    }

    @SuppressWarnings("unchecked")
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane2 = new javax.swing.JScrollPane();
        jTree1 = new javax.swing.JTree();
        defaultTextField1 = new com.gooddies.texteditors.DefaultTextField();
        titleLabel = new javax.swing.JLabel();
        titleEB = new com.gooddies.texteditors.DefaultTextField();
        jScrollPane1 = new javax.swing.JScrollPane();
        descriptionField = new javax.swing.JTextPane();
        descriptionLabel = new javax.swing.JLabel();
        authorLabel = new javax.swing.JLabel();
        authorEB = new com.gooddies.texteditors.DefaultTextField();
        jScrollPane3 = new javax.swing.JScrollPane();
        assetTree = new javax.swing.JTree();
        cancelButton = new javax.swing.JButton();
        createButton = new javax.swing.JButton();
        emailLabel = new javax.swing.JLabel();
        emailEB = new com.gooddies.texteditors.DefaultTextField();
        workProgressBar = new javax.swing.JProgressBar();
        jLabel1 = new javax.swing.JLabel();
        versionEB = new javax.swing.JTextField();

        jScrollPane2.setViewportView(jTree1);

        defaultTextField1.setText("defaultTextField1");

        setDefaultCloseOperation(javax.swing.WindowConstants.DO_NOTHING_ON_CLOSE);
        setTitle("Create Asset");
        setResizable(false);
        addWindowListener(new java.awt.event.WindowAdapter() {
            public void windowClosing(java.awt.event.WindowEvent evt) {
                formWindowClosing(evt);
            }
        });

        titleLabel.setText("Title");

        jScrollPane1.setViewportView(descriptionField);

        descriptionLabel.setText("Description");

        authorLabel.setText("Author");

        javax.swing.tree.DefaultMutableTreeNode treeNode1 = new javax.swing.tree.DefaultMutableTreeNode("Root");
        assetTree.setModel(new javax.swing.tree.DefaultTreeModel(treeNode1));
        assetTree.setCellRenderer(null);
        jScrollPane3.setViewportView(assetTree);

        cancelButton.setText("Cancel");
        cancelButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                cancelButtonActionPerformed(evt);
            }
        });

        createButton.setText("Create");
        createButton.addActionListener(new java.awt.event.ActionListener() {
            public void actionPerformed(java.awt.event.ActionEvent evt) {
                createButtonActionPerformed(evt);
            }
        });

        emailLabel.setText("Email");

        workProgressBar.setFocusable(false);

        jLabel1.setText("Version");

        versionEB.setText("1.0.0");

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(getContentPane());
        getContentPane().setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                    .addComponent(workProgressBar, javax.swing.GroupLayout.DEFAULT_SIZE, 440, Short.MAX_VALUE)
                    .addGroup(layout.createSequentialGroup()
                        .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
                            .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                                .addComponent(titleLabel)
                                .addComponent(titleEB, javax.swing.GroupLayout.DEFAULT_SIZE, 247, Short.MAX_VALUE)
                                .addComponent(authorLabel)
                                .addComponent(authorEB, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(emailLabel)
                                .addComponent(emailEB, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE)
                                .addComponent(versionEB)
                                .addComponent(jScrollPane1))
                            .addComponent(jLabel1)
                            .addComponent(descriptionLabel))
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane3))
                    .addGroup(javax.swing.GroupLayout.Alignment.TRAILING, layout.createSequentialGroup()
                        .addGap(0, 0, Short.MAX_VALUE)
                        .addComponent(createButton)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(cancelButton)))
                .addContainerGap())
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addGroup(layout.createSequentialGroup()
                .addContainerGap()
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING, false)
                    .addGroup(layout.createSequentialGroup()
                        .addComponent(titleLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(titleEB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(authorLabel)
                        .addGap(3, 3, 3)
                        .addComponent(authorEB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(emailLabel)
                        .addGap(1, 1, 1)
                        .addComponent(emailEB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(jLabel1)
                        .addGap(1, 1, 1)
                        .addComponent(versionEB, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.UNRELATED)
                        .addComponent(descriptionLabel)
                        .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                        .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 116, javax.swing.GroupLayout.PREFERRED_SIZE))
                    .addComponent(jScrollPane3, javax.swing.GroupLayout.PREFERRED_SIZE, 0, Short.MAX_VALUE))
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addComponent(workProgressBar, javax.swing.GroupLayout.PREFERRED_SIZE, javax.swing.GroupLayout.DEFAULT_SIZE, javax.swing.GroupLayout.PREFERRED_SIZE)
                .addPreferredGap(javax.swing.LayoutStyle.ComponentPlacement.RELATED)
                .addGroup(layout.createParallelGroup(javax.swing.GroupLayout.Alignment.BASELINE)
                    .addComponent(createButton)
                    .addComponent(cancelButton))
                .addContainerGap(javax.swing.GroupLayout.DEFAULT_SIZE, Short.MAX_VALUE))
        );

        pack();
    }// </editor-fold>//GEN-END:initComponents

    private void createButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_createButtonActionPerformed
        create();
    }//GEN-LAST:event_createButtonActionPerformed

    private void cancelButtonActionPerformed(java.awt.event.ActionEvent evt) {//GEN-FIRST:event_cancelButtonActionPerformed
        cancel();
    }//GEN-LAST:event_cancelButtonActionPerformed

    private void formWindowClosing(java.awt.event.WindowEvent evt) {//GEN-FIRST:event_formWindowClosing
        if (!inProcess) {
            cancel();
        }
    }//GEN-LAST:event_formWindowClosing
    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JTree assetTree;
    private com.gooddies.texteditors.DefaultTextField authorEB;
    private javax.swing.JLabel authorLabel;
    private javax.swing.JButton cancelButton;
    private javax.swing.JButton createButton;
    private com.gooddies.texteditors.DefaultTextField defaultTextField1;
    private javax.swing.JTextPane descriptionField;
    private javax.swing.JLabel descriptionLabel;
    private com.gooddies.texteditors.DefaultTextField emailEB;
    private javax.swing.JLabel emailLabel;
    private javax.swing.JLabel jLabel1;
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JScrollPane jScrollPane2;
    private javax.swing.JScrollPane jScrollPane3;
    private javax.swing.JTree jTree1;
    private com.gooddies.texteditors.DefaultTextField titleEB;
    private javax.swing.JLabel titleLabel;
    private javax.swing.JTextField versionEB;
    private javax.swing.JProgressBar workProgressBar;
    // End of variables declaration//GEN-END:variables

    void create() {
        if (!validateCreationData()) {
            return;
        }
        saveUserData();
        workProgressBar.setMinimum(0);
        workProgressBar.setValue(0);
        workProgressBar.setMaximum(countElements((DefaultMutableTreeNode) getTreeModel().getRoot()));
        currentProcessedFile = 0;
        hFileChooser fileChooser = new hFileChooser() {
            @Override
            public void approveSelection() {
                File f = getSelectedFile();
                if (f.exists() && getDialogType() == SAVE_DIALOG) {
                    int result = JOptionPane.showConfirmDialog(this,
                            "The file exists, overwrite?", "Existing file",
                            JOptionPane.YES_NO_CANCEL_OPTION);
                    switch (result) {
                        case JOptionPane.YES_OPTION:
                            super.approveSelection();
                            return;
                        case JOptionPane.CANCEL_OPTION:
                            cancelSelection();
                            return;
                        default:
                            return;
                    }
                }

                super.approveSelection();
            }
        };
        fileChooser.setFileFilter("Assets files(*.hasset)|hasset");
        if (JFileChooser.APPROVE_OPTION == fileChooser.showSaveDialog(this)) {
            try {
                String path = fileChooser.getSelectedFile().getAbsolutePath();
                if (!path.endsWith(ASSET_FILE_EXTENSION)) {
                    path += ASSET_FILE_EXTENSION;
                }

                createButton.setEnabled(false);
                cancelButton.setEnabled(false);
                titleEB.setEnabled(false);
                authorEB.setEnabled(false);
                emailEB.setEnabled(false);
                versionEB.setEnabled(false);
                descriptionField.setEnabled(false);
                inProcess = true;
                saveData(path);
            } catch (IOException ex) {
                Wiring.getComponent(Utils.class).errorMessage("Cannot save asset file. " + ex.getMessage());
            }
        }
    }

    private void loadUserData() {
        authorEB.setText(Properties.get().getString("assetAuthor", ""));
        emailEB.setText(Properties.get().getString("assetEmail", ""));
    }

    private void saveUserData() {
        Properties.get().putString("assetAuthor", authorEB.getText());
        Properties.get().putString("assetEmail", emailEB.getText());
    }

    void cancel() {
        setVisible(false);
    }

    private DefaultTreeModel getTreeModel() {
        return (DefaultTreeModel) assetTree.getModel();
    }

    private void fillTree() {
        AssetsTree basicTree = Wiring.getComponent(AssetsTree.class);
        TreePath[] selectedPath = basicTree.getSelectionPaths();
        DefaultMutableTreeNode root = new DefaultMutableTreeNode("Root");
        getTreeModel().setRoot(root);
        formatBranch(selectedPath);
        getTreeModel().nodeStructureChanged(root);
    }

    private void formatBranch(TreePath[] selectedPaths) {
        for (TreePath path : selectedPaths) {
            addTreePath(getTreeModel(), path);
        }
    }

    private boolean containsChildren(DefaultMutableTreeNode parent, TreeNode checkedChild) {
        int childCount = parent.getChildCount();
        for (int i = 0; i < childCount; i++) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) parent.getChildAt(i);
            if (node.toString().equals(checkedChild.toString())) {
                return true;
            }
        }
        return false;
    }

    private boolean checkPathIsDirectory(TreePath path) {
        return Wiring.getComponent(AssetsTree.class).isTreePathFolder(path);
    }

    private void addTreePath(DefaultTreeModel treeModel, TreePath path) {
        DefaultMutableTreeNode root = (DefaultMutableTreeNode) treeModel.getRoot();
        DefaultMutableTreeNode currentNode = root;

        for (int i = 1; i < path.getPath().length; i++) {
            DefaultMutableTreeNode pathPart = (DefaultMutableTreeNode) path.getPathComponent(i);
            if (!containsChildren(currentNode, pathPart)) {
                DefaultMutableTreeNode newNode = new DefaultMutableTreeNode(pathPart.toString());
                if (checkPathIsDirectory(path)) {
                    Wiring.getComponent(AssetsTree.class).populateNode(path, newNode, treeModel);
                    newNode.setAllowsChildren(true);
                } else {
                    newNode.setAllowsChildren(false);
                }
                currentNode.add(newNode);
                currentNode = newNode;
            }
        }
    }

    private boolean checkEmail(String email) {
        return Pattern.compile("^(\\S+)@([a-z0-9-]+)(\\.)([a-z]{2,4})(\\.?)([a-z]{0,4})+$").matcher(email).matches();
    }

    private boolean validateCreationData() {
        if (titleEB.getText().trim().isEmpty()) {
            ToolTipHandler.error(titleEB, "Please provide title");
            return false;
        }
        if (authorEB.getText().trim().isEmpty()) {
            ToolTipHandler.error(authorEB, "Please provide author");
            return false;
        }
        if (emailEB.getText().trim().isEmpty()) {
            ToolTipHandler.error(emailEB, "Please provide email");
            return false;
        }
        if (!checkEmail(emailEB.getText().trim())) {
            ToolTipHandler.error(emailEB, "Email is not valid");
            return false;
        }
        if (versionEB.getText().trim().isEmpty()) {
            ToolTipHandler.error(versionEB, "Version is not provided");
            return false;
        }
        if (descriptionField.getText().trim().isEmpty()) {
            ToolTipHandler.error(descriptionField, "Please provide description");
            return false;
        }

        return true;
    }

    private String generateMetaInfoXml() {
        StringBuilder sb = new StringBuilder();
        sb.append("<xml>\n");
        sb.append("\t<title>\n\t\t");
        sb.append(StringEscapeUtils.escapeXml(titleEB.getText())).append("\n");
        sb.append("\t</title>\n");

        sb.append("\t<author>\n\t\t");
        sb.append(StringEscapeUtils.escapeXml(authorEB.getText())).append("\n");
        sb.append("\t</author>\n");

        sb.append("\t<email>\n\t\t");
        sb.append(StringEscapeUtils.escapeXml(emailEB.getText())).append("\n");
        sb.append("\t</email>\n");

        sb.append("\t<version>\n\t\t");
        sb.append(StringEscapeUtils.escapeXml(versionEB.getText())).append("\n");
        sb.append("\t</version>\n");

        sb.append("\t<description>\n\t\t");
        sb.append(StringEscapeUtils.escapeXml(descriptionField.getText())).append("\n");
        sb.append("\t</description>\n");

        sb.append("</xml>");
        return sb.toString();
    }

    @SuppressWarnings("unchecked")
    private int countElements(DefaultMutableTreeNode root) {
        int count = 0;
        Enumeration<DefaultMutableTreeNode> enumarator = root.depthFirstEnumeration();
        while (enumarator.hasMoreElements()) {
            enumarator.nextElement();
            count++;
        }
        return count;
    }

    private void progress() {
        SwingUtilities.invokeLater(new Runnable() {
            @Override
            public void run() {
                currentProcessedFile++;
                workProgressBar.setValue(currentProcessedFile);
            }
        });
    }

    private void saveData(final String path) throws FileNotFoundException, IOException {
        SwingWorker worker = new SwingWorker() {
            @Override
            protected Object doInBackground() throws Exception {
                FileOutputStream stream = new FileOutputStream(new File(path));
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(stream);
                try (ZipOutputStream zstr = new ZipOutputStream(bufferedOutputStream)) {
                    zstr.setLevel(9);
                    zstr.putNextEntry(new ZipEntry("metadata.xml"));
                    zstr.write(generateMetaInfoXml().getBytes("UTF-8"));
                    zstr.closeEntry();
                    zstr.putNextEntry(new ZipEntry("data/"));
                    progress();
                    DefaultMutableTreeNode node = (DefaultMutableTreeNode) getTreeModel().getRoot();
                    savePathToZip(zstr, new TreePath(node));
                    zstr.closeEntry();
                }

                return true;
            }

            @Override
            protected void done() {
                Wiring.getComponent(Utils.class).showMessage("File saved");
                inProcess = false;
                AssetArchiver.this.cancel();
            }
        };
        worker.execute();
    }

    private void copyFileToZipStream(final File file, ZipOutputStream zstream, String zipEntryName) {
        try {
            zstream.putNextEntry(new ZipEntry(zipEntryName));
            try (BufferedInputStream stream = new BufferedInputStream(new FileInputStream(file))) {
                for (;;) {
                    int value = stream.read();
                    if (value == -1) {
                        break;
                    }

                    zstream.write(value);
                }
            }
            zstream.closeEntry();
        } catch (final Exception ex) {
            try {
                SwingUtilities.invokeAndWait(new Runnable() {
                    @Override
                    public void run() {
                        Wiring.getComponent(Utils.class).errorMessage("Cannot write file " + file.getAbsolutePath() + " to archive. Skipped. " + ex.getLocalizedMessage());
                    }
                });
            } catch (InterruptedException | InvocationTargetException ex1) {
                Logger.getLogger(AssetArchiver.class.getName()).log(Level.SEVERE, null, ex1);
            }
        }
    }

    private String formatZipName(TreePath path) {
        StringBuilder sb = new StringBuilder();
        int count = path.getPathCount();
        sb.append("data/");
        boolean first = true;
        for (int i = 1; i < count; i++) {
            DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getPathComponent(i);
            if (!first) {
                sb.append("/");
            }

            sb.append(node.toString());
            first = false;
        }
        return sb.toString();
    }

    private void savePathToZip(ZipOutputStream stream, TreePath path) throws IOException {
        DefaultMutableTreeNode node = (DefaultMutableTreeNode) path.getLastPathComponent();
        for (int i = 0; i < node.getChildCount(); i++) {
            progress();
            DefaultMutableTreeNode child = (DefaultMutableTreeNode) node.getChildAt(i);
            TreePath currentPath = path.pathByAddingChild(child);
            if (Wiring.getComponent(AssetsTree.class).isTreePathFolder(currentPath)) {
                stream.putNextEntry(new ZipEntry(formatZipName(currentPath) + "/"));
                savePathToZip(stream, currentPath);
                stream.closeEntry();
            } else {
                String filePath = Wiring.getComponent(AssetsTree.class).treePathToFilePath(currentPath);
                copyFileToZipStream(new File(filePath), stream, formatZipName(currentPath));
                //save additional files.
                //saving animation .anim files with models
                if (Wiring.getComponent(AssetsTree.class).isTreePath3dModel(currentPath)) {
                    String animationPath = Wiring.getComponent(Utils.class).formatPathWithNewExtension(filePath, ".anim");
                    if (new File(animationPath).exists()) {
                        copyFileToZipStream(new File(animationPath), stream, Wiring.getComponent(Utils.class).formatPathWithNewExtension(formatZipName(currentPath), ".anim"));
                    }
                }
            }
        }
    }
}